#region Copyright (c) 2003, newtelligence AG. All rights reserved.

/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/

#endregion

using System;
using System.Collections;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using newtelligence.DasBlog.Runtime;
using newtelligence.DasBlog.Util;
using newtelligence.DasBlog.Web.Core.Config;

namespace newtelligence.DasBlog.Web.Core.Macros
{
    /// <summary>
    /// Like Macros and DayMacros, ItemMacros always return controls.
    /// </summary>
    public class ItemMacros : DayMacros
    {
        protected Entry entry;

        #region Other

        /// <summary>
        /// Ignored in this version.
        /// </summary>
        public virtual Control Enclosure
        {
            get
            {
                if (entry.Enclosure != null)
                {
                    string linkText = String.Format("<a href=\"{0}\">{1} ({2:#.##} {3})</a>",
                                                    SiteUtilities.GetEnclosureLinkUrl(entry.EntryId, entry.Enclosure),
                                                    entry.Enclosure.Name,
                                                    (entry.Enclosure.Length > 1024*1024)
                                                        ? (entry.Enclosure.Length/(1024.0*1024.0))
                                                        : (entry.Enclosure.Length/1024.0),
                                                    (entry.Enclosure.Length > 1024*1024) ? "MB" : "KB");

                    return new LiteralControl(linkText);
                }
                else
                {
                    return new LiteralControl("");
                }
            }
        }

        /// <summary>
        /// Renders a list of hyperlinks that turn Categories into TechnoratiTags.
        /// Corresponds to the macro: &lt;%TechnoratiTags%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML.
        /// <P/>
        /// The list is created depending on the existing weblog entries.
        /// Entries are parsed and categories are generated on-the-fly.
        /// In case there is no entry for a specific category it 'disappears'.
        /// </remarks>
        public virtual Control TechnoratiTags
        {
            get { return RenderCategoryLinks(true); }
        }

        /// <summary>
        /// Counts the comments on weblog entries.
        /// Corresponds to the macro: &lt;%CommentsCount(...)%&gt;
        /// </summary>
        /// <example>&lt;% CommentsCount("&nbsp;") %&gt;&lt;% IfCommentsRawText("Comments, give one|DE:Kommentare, einen verfasssen", "Comment|DE:Kommentar", "Comments|DE:Kommentare") %&gt;</example>
        /// <param name="separatorText">Text that will be displayed as the separator (after the sum).
        /// </param>
        /// <remarks>
        /// The output is raw text.
        /// </remarks>
        public virtual Control CommentsCount(string separatorText)
        {
            if (requestPage.SiteConfig.EnableComments)
            {
                bool allComments = SiteSecurity.IsValidContributor();
                CommentCollection comments = requestPage.DataService.GetCommentsFor(entry.EntryId, allComments);
                return new LiteralControl(comments.Count + separatorText);
            }
            else
            {
                return new PlaceHolder();
            }
        }

        private static string GetClientUrl(string url, string removeUrlFragmentRegex)
        {
            if (!String.IsNullOrEmpty(removeUrlFragmentRegex))
            {
                url = Regex.Replace(url,
                                    removeUrlFragmentRegex,
                                    String.Empty,
                                    RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
            }

            return Util.ReplaceViaRegEx(url, removeUrlFragmentRegex, String.Empty);
        }

        public virtual Control FeedFlare()
        {
            const string scriptTag =
                "<script src=\"http://feeds.feedburner.com/~s/{0}?i={1}\" type=\"text/javascript\"></script>";

            string feedburnerName = requestPage.SiteConfig.FeedBurnerName;
            if (feedburnerName != null && feedburnerName.Length > 0)
            {
                string script =
                    string.Format(scriptTag, feedburnerName,
                                  HttpUtility.HtmlEncode(SiteUtilities.GetPermaLinkUrl(requestPage.SiteConfig, entry)));
                return new LiteralControl(script);
            }

            return null;
        }

        // TODO: Documentation for maxLength

        #endregion

        #region Date/Time

        /// <summary>
        /// The DateTime (formatted like DateTime.Now) the entry was modified.
        /// Corresponds to the macro: &lt;%modified%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// </remarks>
        public virtual Control Modified
        {
            get
            {
                if (DateTime.Compare(entry.CreatedUtc, entry.ModifiedUtc) == 0)
                {
                    return new LiteralControl();
                }

                if (requestPage.SiteConfig.AdjustDisplayTimeZone)
                {
                    return
                        new LiteralControl(
                            requestPage.SiteConfig.GetConfiguredTimeZone().FormatAdjustedUniversalTime(entry.ModifiedUtc));
                }
                else
                {
                    return new LiteralControl(entry.ModifiedUtc.ToString("U") + " UTC");
                }
            }
        }

        /// <summary>
        /// This macro injects a bit of JavaScript into the output stream that will render
        /// the entry's time in the user's browser using his/her local timezone.
        /// </summary>
        public virtual Control ReaderWhen
        {
            get
            {
                string controlString =
                    String.Format(
                        "<script type=\"text/javascript\">fmtDate = new Date();fmtDate.setTime(Date.parse('{0}'));" +
                        "document.write( fmtDate.toLocaleString() )</script>",
                        entry.CreatedUtc.ToString("r", CultureInfo.InvariantCulture));
                return new LiteralControl(controlString);
            }
        }

        /// <summary>
        /// The DateTime (formatted like DateTime.Now) the entry was created.
        /// Corresponds to the macro: &lt;%when%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// </remarks>
        public virtual Control When
        {
            get
            {
                if (requestPage.SiteConfig.AdjustDisplayTimeZone)
                {
                    return
                        new LiteralControl(
                            requestPage.SiteConfig.GetConfiguredTimeZone().FormatAdjustedUniversalTime(entry.CreatedUtc));
                }
                else
                {
                    return new LiteralControl(entry.CreatedUtc.ToString("U") + " UTC");
                }
            }
        }

        /// <summary>
        /// Returns a formatted DateTime as specified by the format parameter.
        /// Corresponds to the macro: &lt;%formattedWhen(...)%&gta and &lt;%formattedModified(...)%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// </remarks>
        private Control FormattedDate(string format, bool addTimezone, DateTime date)
        {
            DateTime dateValue;
            string name;

            if (requestPage.SiteConfig.AdjustDisplayTimeZone)
            {
                WindowsTimeZone tz = requestPage.SiteConfig.GetConfiguredTimeZone();
                dateValue = tz.ToLocalTime(date);

                if (tz.IsDaylightSavingTime(dateValue))
                {
                    name = tz.DaylightName;
                }
                else
                {
                    name = tz.StandardName;
                }
            }
            else
            {
                dateValue = date;
                name = "UTC";
            }
            if (addTimezone)
            {
                return new LiteralControl(string.Format("{0} {1}", dateValue.ToString(format), name));
            }
            else
            {
                return new LiteralControl(string.Format("{0}", dateValue.ToString(format)));
            }
        }

        public virtual Control FormattedModified(string format)
        {
            if (DateTime.Compare(entry.CreatedUtc, entry.ModifiedUtc) == 0)
            {
                return new LiteralControl();
            }

            return FormattedDate(format, true, entry.ModifiedUtc);
        }

        public virtual Control FormattedModifiedBare(string format)
        {
            if (DateTime.Compare(entry.CreatedUtc, entry.ModifiedUtc) == 0)
            {
                return new LiteralControl();
            }

            return FormattedDate(format, false, entry.ModifiedUtc);
        }

        public virtual Control FormattedWhen(string format)
        {
            return FormattedDate(format, true, entry.CreatedUtc);
        }

        public virtual Control FormattedWhenBare(string format)
        {
            return FormattedDate(format, false, entry.CreatedUtc);
        }

        public virtual Control IfModified(string expression)
        {
            if (DateTime.Compare(entry.CreatedUtc, entry.ModifiedUtc) == 0)
            {
                return new LiteralControl();
            }

            return LocalString(expression);
        }

        #endregion

        #region GeoRSS

        public virtual Control Latitude
        {
            get
            {
                Control control = null;

                if (requestPage.SiteConfig.EnableGeoRss)
                {
                    Nullable<double> latitude = new Nullable<double>();

                    if (entry.Latitude.HasValue)
                    {
                        latitude = entry.Latitude;
                    }
                    else
                    {
                        if (requestPage.SiteConfig.EnableDefaultLatLongForNonGeoCodedPosts)
                        {
                            latitude = requestPage.SiteConfig.DefaultLatitude;
                        }
                    }

                    if (latitude.HasValue)
                    {
                        control = new LiteralControl(String.Format(CultureInfo.InvariantCulture, "{0}", latitude));
                    }
                }

                return control;
            }
        }

        public virtual Control Longitude
        {
            get
            {
                Control control = null;

                if (requestPage.SiteConfig.EnableGeoRss)
                {
                    Nullable<double> longitude = new Nullable<double>();

                    if (entry.Longitude.HasValue)
                    {
                        longitude = entry.Longitude;
                    }
                    else
                    {
                        if (requestPage.SiteConfig.EnableDefaultLatLongForNonGeoCodedPosts)
                        {
                            longitude = requestPage.SiteConfig.DefaultLongitude;
                        }
                    }

                    if (longitude.HasValue)
                    {
                        control = new LiteralControl(String.Format(CultureInfo.InvariantCulture, "{0}", longitude));
                    }
                }

                return control;
            }
        }

        public virtual Control LatitudeLongitude(string prefix, string latLongSeparator, string suffix)
        {
            Control control = null;

            if (requestPage.SiteConfig.EnableGeoRss)
            {
                Nullable<double> latitude = new Nullable<double>();
                Nullable<double> longitude = new Nullable<double>();

                if (entry.Latitude.HasValue)
                {
                    latitude = entry.Latitude;
                }
                else
                {
                    if (requestPage.SiteConfig.EnableDefaultLatLongForNonGeoCodedPosts)
                    {
                        latitude = requestPage.SiteConfig.DefaultLatitude;
                    }
                }

                if (entry.Longitude.HasValue)
                {
                    longitude = entry.Longitude;
                }
                else
                {
                    if (requestPage.SiteConfig.EnableDefaultLatLongForNonGeoCodedPosts)
                    {
                        longitude = requestPage.SiteConfig.DefaultLongitude;
                    }
                }

                if (latitude.HasValue && longitude.HasValue)
                {
                    control =
                        new LiteralControl(
                            String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}", prefix, latitude,
                                          latLongSeparator, longitude, suffix));
                }
            }

            return control;
        }

        #endregion

        #region IfElse

        /// <summary>
        /// A placeholder for rendering a raw permalink for each single weblog entry if the page is an aggregated view.
        /// Corresponds to the macro: &lt;%IfViewPermalinkUrlRaw%&gt;
        /// </summary>
        /// <remarks>
        /// The output is raw text, url encoded.
        /// </remarks>
        public virtual Control IfViewPermalinkUrlRaw
        {
            get
            {
                if (requestPage.IsAggregatedView)
                {
                    return new LiteralControl(SiteUtilities.GetPermaLinkUrl(entry));
                }
                else
                {
                    return new PlaceHolder();
                }
            }
        }

        /// <summary>
        /// Shows a localized raw text, if the user is in admin role.
        /// Corresponds to the macro: &lt;%IfAdminRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% IfAdminRawText("Administration area|DE: Administration", "Reader area|DE: Leser") %&gt;</example>
        /// <param name="ifIsAdminExpression">Text that will be displayed if the user is in admin role.</param>
        /// <param name="elsewiseExpression">Text that will be displayed f the user is in admin role.</param>
        /// <returns>The output is localized raw text.</returns>
        public virtual Control IfAdminRawText(String ifIsAdminExpression, String elsewiseExpression)
        {
            return IfAdminRawText(ifIsAdminExpression, elsewiseExpression, null);
        }

        /// <summary>
        /// Shows a localized raw text, if the user is in admin role.
        /// Corresponds to the macro: &lt;%IfAdminRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% IfAdminRawText("Administration area|DE: Administration", "Reader area|DE: Leser") %&gt;</example>
        /// <param name="ifIsAdminExpression">Text that will be displayed if the user is in admin role.</param>
        /// <param name="elsewiseExpression">Text that will be displayed f the user is in admin role.</param>
        /// <param name="quotRegEx">Regular expression. If it matches, the match will be replaced with an ".</param>
        /// <returns>The output is localized raw text.</returns>
        public virtual Control IfAdminRawText(String ifIsAdminExpression, String elsewiseExpression, string quotRegEx)
        {
            string rawText;

            if (SiteSecurity.IsInRole(CoreSecurityNames.Roles.Admin))
            {
                if (!String.IsNullOrEmpty(quotRegEx))
                {
                    rawText = Util.ReplaceViaRegEx(ifIsAdminExpression, quotRegEx, "\"");
                }
                else
                {
                    rawText = ifIsAdminExpression;
                }
            }
            else
            {
                if (!String.IsNullOrEmpty(quotRegEx))
                {
                    rawText = Util.ReplaceViaRegEx(elsewiseExpression, quotRegEx, "\"");
                }
                else
                {
                    rawText = elsewiseExpression;
                }
            }

            return new LiteralControl(rawText);
        }

        /// <summary>
        /// Shows a localized raw text, if there is at least one comment.
        /// Corresponds to the macro: &lt;%IfCommentsRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% CommentsCount("&nbsp;") %&gt;&lt;% IfCommentsRawText("Comments, give one|DE:Kommentare, einen verfasssen", "Comment|DE:Kommentar", "Comments|DE:Kommentare") %&gt;</example>
        /// <param name="commentTextSingular">Text that will be displayed if there is no or one comment.</param>
        /// <param name="commentTextPlural">Text that will be displayed if there is more than one comment.</param>
        /// </param>
        /// <remarks>
        /// The output is localized raw text.
        /// </remarks>
        public virtual Control IfCommentsRawText(String commentTextZero, String commentTextSingular,
                                                 String commentTextPlural)
        {
            if (requestPage.SiteConfig.EnableComments)
            {
                bool allComments = SiteSecurity.IsValidContributor();
                CommentCollection comments = requestPage.DataService.GetCommentsFor(entry.EntryId, allComments);
                String returnText = String.Empty;

                if (comments != null)
                {
                    if (comments.Count == 1)
                    {
                        returnText = commentTextSingular;
                    }
                    else if (comments.Count > 1)
                    {
                        returnText = commentTextPlural;
                    }
                    else if (comments.Count == 0)
                    {
                        returnText = commentTextZero;
                    }
                }

                return new LiteralControl(LocalStringBlank(returnText));
            }
            else
            {
                return new PlaceHolder();
            }
        }

        /// <summary>
        /// Shows a localized raw text, if the page is not an aggregated view.
        /// Corresponds to the macro: &lt;%IfItemRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% IfItemRawText("This is a blog entry|DE: Das ist ein Eintrag", "This is a view|DE: Das ist eine Ansicht") %&gt;</example>
        /// <param name="ifIsItemExpression">Text that will be displayed if the page is not an aggregated view.</param>
        /// <param name="elsewiseExpression">Text that will be displayed if the page is an aggregated view.</param>
        /// <returns>The output is localized raw text.</returns>
        public virtual Control IfItemRawText(String ifIsItemExpression, String elsewiseExpression)
        {
            return IfItemRawText(ifIsItemExpression, elsewiseExpression, null);
        }

        /// <summary>
        /// Shows a localized raw text, if the page is not an aggregated view.
        /// Corresponds to the macro: &lt;%IfItemRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% IfItemRawText("This is a blog entry|DE: Das ist ein Eintrag", "This is a view|DE: Das ist eine Ansicht") %&gt;</example>
        /// <param name="ifIsItemExpression">Text that will be displayed if the page is not an aggregated view.</param>
        /// <param name="elsewiseExpression">Text that will be displayed if the page is an aggregated view.</param>
        /// <param name="quotRegEx">Regular expression. If it matches, the match will be replaced with an ".</param>
        /// <returns>The output is localized raw text.</returns>
        public virtual Control IfItemRawText(String ifIsItemExpression, String elsewiseExpression, string quotRegEx)
        {
            string rawText;

            if (!requestPage.IsAggregatedView)
            {
                if (!String.IsNullOrEmpty(quotRegEx))
                {
                    rawText = Util.ReplaceViaRegEx(ifIsItemExpression, quotRegEx, "\"");
                }
                else
                {
                    rawText = ifIsItemExpression;
                }
            }
            else
            {
                if (!String.IsNullOrEmpty(quotRegEx))
                {
                    rawText = Util.ReplaceViaRegEx(elsewiseExpression, quotRegEx, "\"");
                }
                else
                {
                    rawText = elsewiseExpression;
                }
            }

            return new LiteralControl(rawText);
        }

        /// <summary>
        /// Shows a localized raw text if the obtained param has the expected value.
        /// Corresponds to the macro: &lt;%IfParamRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% IfParamRawText("showComments", "true", "style=display: none;", "style=display: block;") %&gt;</example>
        /// <param name="paramName">A param name.</param>
        /// <param name="paramValue">The obtained value of the param.</param>
        /// <param name="expression">Text that will be displayed if the param has the obtained value.</param>
        /// <param name="elsewiseExpression">Text that will be displayed otherwise.</param>
        /// <returns>The output is localized raw text.</returns>
        public virtual Control IfParamRawText(String paramName, String paramValue, String expression,
                                              String elsewiseExpression)
        {
            return IfParamRawText(paramName, paramValue, expression, elsewiseExpression, null);
        }

        /// <summary>
        /// Shows a localized raw text if the obtained param has the expected value.
        /// Corresponds to the macro: &lt;%IfParamRawText(...)%&gt;
        /// </summary>
        /// <example>&lt;% IfParamRawText("showComments", "true", "style=display: none;", "style=display: block;") %&gt;</example>
        /// <param name="paramName">A param name.</param>
        /// <param name="paramValue">The obtained value of the param.</param>
        /// <param name="expression">Text that will be displayed if the param has the obtained value.</param>
        /// <param name="elsewiseExpression">Text that will be displayed otherwise.</param>
        /// <param name="quotRegEx">Regular expression. If it matches, the match will be replaced with an ".</param>
        /// <returns>The output is localized raw text.</returns>
        public virtual Control IfParamRawText(String paramName, String paramValue, String expression,
                                              String elsewiseExpression, String quotRegEx)
        {
            string rawText;

            if (String.IsNullOrEmpty(paramName) || paramValue == null)
            {
                return new PlaceHolder();
            }

            if (requestPage.Request.Params[paramName] == paramValue)
            {
                if (!String.IsNullOrEmpty(quotRegEx))
                {
                    rawText = Util.ReplaceViaRegEx(expression, quotRegEx, "\"");
                }
                else
                {
                    rawText = expression;
                }
            }
            else
            {
                if (!String.IsNullOrEmpty(quotRegEx))
                {
                    rawText = Util.ReplaceViaRegEx(elsewiseExpression, quotRegEx, "\"");
                }
                else
                {
                    rawText = elsewiseExpression;
                }
            }

            return new LiteralControl(rawText);
        }

        #endregion

        #region Item

        /// <summary>
        /// ItemMacros' constructor
        /// </summary>
        /// <param name="page">The page we are rendering</param>
        /// <param name="entry">An entry!</param>
        public ItemMacros(SharedBasePage page, Entry entry)
            : base(page, entry.CreatedUtc)
        {
            this.entry = entry;
        }

        /// <summary>
        /// A placeholder for rendering HTML hyperlinks for each single weblog entry.
        /// Corresponds to the macro: &lt;%editButton%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML.
        /// <P/>
        /// If the user IsInRole("admin") then he should be able to edit or delete entries.
        /// In this case there are appropriate hyperlinked images rendered to the page.
        /// </remarks>
        public virtual Control EditButton
        {
            get
            {
                PlaceHolder placeHolder = new PlaceHolder();

                if (!requestPage.HideAdminTools &&
                    // enable edit and delete buttons under the following conditions:
                    // the user is an administrator, or
                    // the user is a contributor and they wrote the entry
                    // (contributors should not have access to entries they didn't write!!)
                    (SiteSecurity.IsInRole("admin") ||
                     (SiteSecurity.IsInRole("contributor") &&
                      HttpContext.Current.User.Identity.Name == entry.Author)))
                {
                    HyperLink hl = new HyperLink();
                    hl.CssClass = "editLinkStyle";
                    Image img = new Image();
                    img.CssClass = "editLinkImageStyle";
                    img.ImageUrl =
                        new Uri(new Uri(SiteUtilities.GetBaseUrl(requestPage.SiteConfig)),
                                requestPage.GetThemedImageUrl("editbutton")).ToString();
                    img.BorderWidth = 0;
                    img.AlternateText = SharedBasePage.CoreStringTables.GetString("alttext_editbutton");
                    hl.Controls.Add(img);
                    hl.NavigateUrl = SiteUtilities.GetEditViewUrl(entry.EntryId);
                    hl.ToolTip = HttpUtility.HtmlEncode("Edit " + entry.Title);

                    placeHolder.Controls.Add(hl);
                    hl = new HyperLink();
                    hl.ToolTip = HttpUtility.HtmlEncode("Delete " + entry.Title);
                    hl.CssClass = "deleteLinkStyle";
                    img = new Image();
                    img.CssClass = "deleteLinkImageStyle";
                    img.ImageUrl =
                        new Uri(new Uri(SiteUtilities.GetBaseUrl(requestPage.SiteConfig)),
                                requestPage.GetThemedImageUrl("deletebutton")).ToString();
                    img.BorderWidth = 0;
                    img.AlternateText = SharedBasePage.CoreStringTables.GetString("alttext_deletebutton");
                    hl.Controls.Add(img);
                    hl.NavigateUrl =
                        String.Format("javascript:deleteEntry(\"{0}\", \"{1}\")", entry.EntryId,
                                      entry.Title.Replace("\"", "\\\""));
                    placeHolder.Controls.Add(hl);
                }

                return placeHolder;
            }
        }

        /// <summary>
        /// The body of a single weblog entry. Corresponds to the macro: &lt;%itemBody%&gt;
        /// </summary>
        /// <remarks>
        /// The difference between the output of this and the itemText macro is that
        /// this macro depends on the &lt;ShowDescriptionInAggregatedViews&gt; setting in
        /// site.config and on whether the current page is an aggregated view. If both is the 
        /// case and the entry does have a description in addition to a content entry, the
        /// description and not the body content will be rendered. If the description is rendered,
        /// the macro will add a "Read more..." link at the bottom of the rendered output.
        /// <P/>
        /// To de/activate the content filter mechanism edit the tag
        /// &lt;ApplyContentFiltersToWeb&gt; in the SiteConfig file of the Web application.
        /// </remarks>
        public virtual Control ItemBody
        {
            get
            {
                bool isDescription = false;
                string bodyContent;

                if (requestPage.SiteConfig.ShowItemDescriptionInAggregatedViews && requestPage.IsAggregatedView)
                {
                    if (entry.Description != null && entry.Description.Trim().Length > 0)
                    {
                        bodyContent = entry.Description;
                        isDescription = true;
                    }
                    else
                    {
                        bodyContent = entry.Content;
                    }
                }
                else
                {
                    bodyContent = entry.Content;
                }

                if (requestPage.SiteConfig.ApplyContentFiltersToWeb)
                {
                    bodyContent = SiteUtilities.FilterContent(entry.EntryId, bodyContent);
                }

                if (isDescription)
                {
                    bodyContent +=
                        String.Format(
                            "<div class=\"itemReadMoreStyle\"><a href=\"{0}\" rel=\"bookmark\">{1}</a></div>",
                            SiteUtilities.GetPermaLinkUrl(requestPage.SiteConfig, entry),
                            SharedBasePage.CoreStringTables.GetString("text_read_more"));
                }

                if (!requestPage.HideAdminTools && entry.Language != null && entry.Language.Length > 0)
                {
                    CultureInfo ci = CultureInfo.InvariantCulture;
                    foreach (CultureInfo sci in CultureInfo.GetCultures(CultureTypes.AllCultures))
                    {
                        if (sci.Name.ToUpper() == entry.Language.ToUpper())
                        {
                            ci = sci;
                            break;
                        }
                    }

                    bodyContent =
                        String.Format("<p style=\"color:red;font-size:smaller\">{0} {1}/{2} ({3})<p>{4}",
                                      SharedBasePage.CoreStringTables.GetString("text_item_lang_note"), ci.EnglishName,
                                      ci.NativeName, entry.Language, bodyContent);
                }

                if (!requestPage.HideAdminTools && (entry.Categories == null || entry.Categories.Trim().Length == 0) &&
                    requestPage.SiteConfig.FrontPageCategory != null &&
                    requestPage.SiteConfig.FrontPageCategory.Length > 0)
                {
                    bodyContent = "<p style=\"color:red;font-size:smaller\">" +
                                  SharedBasePage.CoreStringTables.GetString("text_item_cat_note") + "<p>" + bodyContent;
                }

                return new LiteralControl(bodyContent);
            }
        }

        /// <summary>
        /// The Author of a single weblog entry.
        /// Corresponds to the macro: &lt;%itemAuthor%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// </remarks>
        public virtual Control ItemAuthor
        {
            get
            {
                User user = SiteSecurity.GetUser(entry.Author);

                if (user != null)
                {
                    if (user.EmailAddress != null && user.EmailAddress.Length > 0)
                    {
                        StringBuilder builder = new StringBuilder();
                        builder.Append("<a href=\"");

                        if (requestPage.SiteConfig.ObfuscateEmail)
                        {
                            builder.Append(
                                SiteUtilities.GetObfuscatedEmailUrl(user.EmailAddress, entry.Title.Replace("'", "\\'")));
                        }
                        else
                        {
                            builder.Append("mailto:");
                            builder.Append(user.EmailAddress);
                        }

                        builder.Append("\">");

                        if (user.DisplayName != null && user.DisplayName.Length > 0)
                        {
                            builder.Append(user.DisplayName);
                        }
                        else
                        {
                            builder.Append(entry.Author);
                        }
                        builder.Append("</a>");

                        return new LiteralControl(builder.ToString());
                    }
                }

                return new LiteralControl(entry.Author);
            }
        }

        /// <summary>
        /// The Author of a single weblog entry.
        /// Corresponds to the macro: &lt;%itemAuthorNoLink%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// </remarks>
        public virtual Control ItemAuthorRaw
        {
            get
            {
                User user = SiteSecurity.GetUser(entry.Author);

                if (user != null && user.DisplayName != null && user.DisplayName.Length > 0)
                {
                    return new LiteralControl(user.DisplayName);
                }

                return new LiteralControl(entry.Author);
            }
        }

        /// <summary>
        /// The Description of a single weblog entry.
        /// Corresponds to the macro: &lt;%itemDescription%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// <P/>
        /// To de/activate the content filter mechanism edit the tag
        /// &lt;ApplyContentFiltersToWeb&gt; in the SiteConfig file of the Web application.
        /// </remarks>
        public virtual Control ItemDescription
        {
            get
            {
                if (requestPage.SiteConfig.ApplyContentFiltersToWeb)
                {
                    return new LiteralControl(SiteUtilities.FilterContent(entry.EntryId, entry.Description));
                }
                else
                {
                    return new LiteralControl(entry.Description);
                }
            }
        }

        /// <summary>
        /// The unique guid of the item.
        /// Corresponds to the macro: &lt;%itemGuid%&gt;
        /// </summary>
        /// <remarks>
        /// The output is raw text.
        /// </remarks>
        public virtual Control ItemGuid
        {
            get { return new LiteralControl(entry.EntryId); }
        }

        /// <summary>
        /// The text of a single weblog entry.
        /// Corresponds to the macro: &lt;%itemText%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// <P/>
        /// To de/activate the content filter mechanism edit the tag
        /// &lt;ApplyContentFiltersToWeb&gt; in the SiteConfig file of the Web application.
        /// </remarks>
        public virtual Control ItemText
        {
            get
            {
                string bodyContent;

                if (requestPage.SiteConfig.ApplyContentFiltersToWeb)
                {
                    bodyContent = SiteUtilities.FilterContent(entry.EntryId, entry.Content);
                }
                else
                {
                    bodyContent = entry.Content;
                }

                if (SiteSecurity.IsInRole("admin") && entry.Language != null && entry.Language.Length > 0)
                {
                    CultureInfo ci = CultureInfo.InvariantCulture;
                    foreach (CultureInfo sci in CultureInfo.GetCultures(CultureTypes.AllCultures))
                    {
                        if (sci.Name.ToUpper() == entry.Language.ToUpper())
                        {
                            ci = sci;
                            break;
                        }
                    }

                    bodyContent =
                        String.Format("<p style=\"color:red;font-size:smaller\">{0} {1}/{2} ({3})<p>{4}",
                                      SharedBasePage.CoreStringTables.GetString("text_item_lang_note"), ci.EnglishName,
                                      ci.NativeName, entry.Language, bodyContent);
                }

                if (SiteSecurity.IsInRole("admin") && (entry.Categories == null || entry.Categories.Trim().Length == 0) &&
                    requestPage.SiteConfig.FrontPageCategory != null &&
                    requestPage.SiteConfig.FrontPageCategory.Length > 0)
                {
                    bodyContent = "<p style=\"color:red;font-size:smaller\">" +
                                  SharedBasePage.CoreStringTables.GetString("text_item_cat_note") + "<p>" + bodyContent;
                }

                return new LiteralControl(bodyContent);
            }
        }

        /// <summary>
        /// The title of a single weblog entry.
        /// Corresponds to the macro: &lt;%itemTitle%&gt;
        /// </summary>
        /// <remarks>
        /// The output is plain text.
        /// <P/>
        /// To de/activate the content filter mechanism edit the tag
        /// &lt;ApplyContentFiltersToWeb&gt; in the SiteConfig file of the Web application.
        /// </remarks>
        public virtual Control ItemTitle
        {
            get
            {
                Control control;
                string title = entry.Title;

                if (requestPage.SiteConfig.EntryTitleAsLink || entry.Link != null)
                {
                    HyperLink hl = new HyperLink();
                    if (entry.Link != null)
                    {
                        hl.NavigateUrl = entry.Link;
                    }
                    else
                    {
                        hl.NavigateUrl = SiteUtilities.GetPermaLinkUrl(entry);
                        // only add the rel="bookmark" when it's actually a permalink
                        hl.Attributes.Add("rel", "bookmark");
                    }
                    hl.Text = entry.Title;
                    hl.CssClass = "TitleLinkStyle";
                    control = hl;
                }
                else
                {
                    if (requestPage.SiteConfig.ApplyContentFiltersToWeb)
                    {
                        control = new LiteralControl(SiteUtilities.FilterContent(entry.EntryId, title));
                    }
                    else
                    {
                        control = new LiteralControl(entry.Title);
                    }
                }

                return control;
            }
        }

        public Control ItemTitleRaw
        {
            get { return new LiteralControl(entry.Title); }
        }

        #endregion

        #region Links

        /// <summary>
        /// Renders a list of user-definable hyperlinks.
        /// Corresponds to the macro: &lt;%categoryLinks%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML.
        /// <P/>
        /// The list is created depending on the existing weblog entries.
        /// Entries are parsed and categories are generated on-the-fly.
        /// In case there is no entry for a specific category it 'disappears'.
        /// </remarks>
        public virtual Control CategoryLinks
        {
            get { return RenderCategoryLinks(false); }
        }

        /// <summary>
        /// An HTML hyperlink to view comments on weblog entries.
        /// Corresponds to the macro: &lt;%commentlink%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML.
        /// <P/>
        /// A counter for counting comments can be de/activated through
        /// &lt;ShowCommentCount&gt; in the SiteConfig file of the Web application.
        /// </remarks>
        public virtual Control Commentlink
        {
            get
            {
                if (requestPage.SiteConfig.EnableComments)
                {
                    if (requestPage.SiteConfig.ShowCommentCount)
                    {
                        bool allComments = SiteSecurity.IsValidContributor();
                        CommentCollection comments = requestPage.DataService.GetCommentsFor(entry.EntryId, allComments);
                        HyperLink hl = new HyperLink();
                        hl.CssClass = "commentLinkStyle";
                        hl.NavigateUrl = SiteUtilities.GetCommentViewUrl(entry.EntryId) + "#commentstart";
                        hl.Text = SharedBasePage.CoreStringTables.GetString("text_comments") + " [" +
                                  comments.Count + "]";

                        return hl;
                    }
                    else
                    {
                        HyperLink hl = new HyperLink();
                        hl.CssClass = "commentLinkStyle";
                        hl.NavigateUrl = SiteUtilities.GetCommentViewUrl(entry.EntryId) + "#commentstart";
                        hl.Text = SharedBasePage.CoreStringTables.GetString("text_comments");

                        return hl;
                    }
                }
                else
                {
                    return new PlaceHolder();
                }
            }
        }

        /// <summary>
        /// An HTML hyperlink pointing to the disclaimer file in siteConfig/disclaimer.format.html
        /// Corresponds to the macro: &lt;%disclaimerLink%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML hyperlink.
        /// </remarks>
        public virtual Control DisclaimerLink
        {
            get
            {
                HyperLink hl = new HyperLink();
                hl.ToolTip = SharedBasePage.CoreStringTables.GetString("tooltip_disclaimer");
                hl.Text = SharedBasePage.CoreStringTables.GetString("text_disclaimer");
                hl.CssClass = "disclaimerLinkStyle";
                hl.NavigateUrl = String.Format("FormatPage.aspx?path={0}", "siteConfig/disclaimer.format.html");

                return hl;
            }
        }

        /// <summary>
        /// A placeholder for rendering an HTML hyperlink for each single weblog entry.
        /// Corresponds to the macro: &lt;%permalink%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML.
        /// </remarks>
        public virtual Control Permalink
        {
            get
            {
                PlaceHolder placeHolder = new PlaceHolder();
                HyperLink hl = new HyperLink();
                hl.CssClass = "permalinkStyle";
                Image img = new Image();
                img.ImageUrl =
                    new Uri(new Uri(SiteUtilities.GetBaseUrl(requestPage.SiteConfig)),
                            requestPage.GetThemedImageUrl("itemLink")).ToString();
                img.CssClass = "permalinkImageStyle";
                img.ToolTip = SharedBasePage.CoreStringTables.GetString("text_permalink");
                img.AlternateText = "#";
                hl.Controls.Add(img);
                hl.NavigateUrl = SiteUtilities.GetPermaLinkUrl(entry);
                hl.Attributes.Add("rel", "bookmark");
                placeHolder.Controls.Add(hl);

                return placeHolder;
            }
        }

        /// <summary>
        /// A placeholder for rendering a formatted, possibly title-based permalink for each single weblog entry.
        /// Corresponds to the macro: &lt;%permalinkUrl%&gt;
        /// </summary>
        /// <remarks>
        /// The output is raw text.
        /// </remarks>
        public virtual Control PermalinkUrl
        {
            get { return new LiteralControl(SiteUtilities.GetPermaLinkUrl(entry)); }
        }

        /// <summary>
        /// A placeholder for rendering a raw permalink for each single weblog entry.
        /// Corresponds to the macro: &lt;%permalinkUrlRaw%&gt;
        /// </summary>
        /// <remarks>
        /// The output is raw text, url encoded
        /// </remarks>
        public virtual Control PermalinkUrlRaw
        {
            get { return new LiteralControl(SiteUtilities.GetPermaLinkUrl(entry)); }
        }

        /// <summary>
        /// An HTML hyperlink.
        /// Corresponds to the macro: &lt;%trackbackLink%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML hyperlink.
        /// </remarks>
        public virtual Control Trackbacklink
        {
            get
            {
                if (requestPage.SiteConfig.EnableTrackbackService)
                {
                    HyperLink hl = new HyperLink();
                    hl.CssClass = "trackbackLinkStyle";
                    hl.NavigateUrl = SiteUtilities.GetTrackbackUrl(entry.EntryId);
                    hl.ToolTip = SharedBasePage.CoreStringTables.GetString("tooltip_trackback");
                    hl.Text = SharedBasePage.CoreStringTables.GetString("text_trackback");

                    return hl;
                }

                return new LiteralControl(String.Empty);
            }
        }

        /// <summary>
        /// Returns a link back to the main site.  This is pretty much the same as the site name link for the main 
        /// page macros - the only difference is that this is only displayed if we are on a single entry.
        /// </summary>
        /// <param name="pageLinkText"></param>
        /// <returns></returns>
        public virtual Control MainPageEntryLink(string pageLinkText)
        {
            if (requestPage.WeblogEntries.Count == 1)
            {
                return
                    new LiteralControl(
                        String.Format("<a href=\"{0}\" rel=\"home\">{1}</a>",
                                      SiteUtilities.GetStartPageUrl(requestPage.SiteConfig), pageLinkText));
            }
            return new LiteralControl("");
        }

        /// <summary>
        /// Returns a link back to the main site.  This is pretty much the same as the site name link for the main 
        /// page macros - the only difference is that this is only displayed if we are on a single entry.
        /// 
        /// This overload will take in an option seperator so taht you can pass in ("Main", "|") and it will
        /// render " | <a href="">Main</a> | " without hyperlinking the sepererator.
        /// </summary>
        /// <param name="pageLinkText"></param>
        /// <param name="pageLinkSeperator">The separator you wish to use like "|"</param>
        /// <returns></returns>
        public virtual Control MainPageEntryLink(string pageLinkText, string pageLinkSeperator)
        {
            if (requestPage.WeblogEntries.Count == 1)
            {
                return
                    new LiteralControl(
                        String.Format(" {2} <a href=\"{0}\" rel=\"home\">{1}</a> {2} ",
                                      SiteUtilities.GetStartPageUrl(requestPage.SiteConfig), pageLinkText,
                                      pageLinkSeperator));
            }

            return new LiteralControl("");
        }

        /// <summary>
        /// Returns a link to the most recent entry if we are on a single entry page.
        /// </summary>
        /// <param name="mostRecentLinkText"></param>
        /// <returns></returns>
        public virtual Control MostRecentEntryLink(string mostRecentLinkText)
        {
            if (requestPage.WeblogEntries.Count == 1 && mostRecentLinkText != null)
            {
                EntryCollection cachedEntryCollection = requestPage.DataService.GetEntries(false);

                // Grab the first entry out of the cache - this is our most recent post.
                int mostRecentIndex = 0;
                Entry mostRecentEntry;

                // Non-published entries don't get returned by the data service.  If our most recent entry in
                // the cache is non-published, keep moving until we find one that is.
                do
                {
                    mostRecentEntry = cachedEntryCollection[mostRecentIndex];
                    ++mostRecentIndex;
                } while (mostRecentEntry == null && mostRecentIndex < cachedEntryCollection.Count);

                if (mostRecentEntry != null)
                {
                    return new LiteralControl(String.Format("<a href=\"{0}\">{1}</a>",
                                                            SiteUtilities.GetPermaLinkUrl(mostRecentEntry),
                                                            mostRecentLinkText));
                }
            }

            return new LiteralControl("");
        }

        /// <summary>
        /// Returns a Hyperlink control linking to the following entry beyond this one.  
        /// The macro is of the form &lt;%nextLink("[Your String Here]")%&gt;  
        /// </summary>  
        /// <param name="nextNavigationText">Whatever text should be appended to the next entry title 
        /// (for example, double right angle brackets.)</param>
        /// <param name="maxLength"></param>
        /// <remarks>The text of this link is
        /// of the form "[Entry Title][NextNavigationText]" (spaces are inserted at the choice of the user).
        /// This string is not HTML-encoded so as to allow the user to insert HTML 
        /// blocks (e.g. images, etc.) in place of text if they so choose. 
        /// </remarks>
        public virtual Control NextLink(string nextNavigationText, int maxLength)
        {
            // We only try to create a previous link in a situation where the user has loaded
            // a page with only one entry (i.e., a specific link), because this is where previous
            // and next links come in handy.
            // If the user, for example, is on the front page or a category page, the "previous" and "next" links
            // have no real point.
            if (requestPage.WeblogEntries.Count == 1)
            {
                // We subtract 5 seconds from the entry time, as otherwise OccursBefore will retrieve
                // our current entry in addition to the entries before it.
                EntryCollection cachedEntryCollection = requestPage.DataService.GetEntries(false);

                // Get our current entry from the cache.
                Entry ourCachedEntry = cachedEntryCollection[entry.EntryId];
                int ourCachedEntryIndex = cachedEntryCollection.IndexOf(ourCachedEntry);
                Entry nextEntry;

                // The data service is sorted in reverse order, so to get the "next post" we need to look at 
                // the entry with an index 1 lower than our current entry.
                if (ourCachedEntryIndex > 0)
                {
                    int nextCachedEntryIndex = ourCachedEntryIndex - 1;

                    // The dataservice will not retrieve entries that have not been published.  So, if we 
                    // grab an id of an entry that hasn't been published yet, we need to go further up the list
                    // to get the next link.  If there are no next posts that have been published, there's
                    // no need to render the next link control.
                    do
                    {
                        nextEntry = cachedEntryCollection[nextCachedEntryIndex];
                        --nextCachedEntryIndex;
                    } while (nextEntry == null &&
                             nextCachedEntryIndex >= 0);

                    if (nextEntry != null)
                    {
                        HyperLink nextLink = new HyperLink();
                        nextLink.Text = Util.TruncateString(nextEntry.Title, maxLength) + nextNavigationText;

                        if (nextEntry.Link != null)
                        {
                            nextLink.NavigateUrl = nextEntry.Link;
                        }
                        else
                        {
                            nextLink.NavigateUrl = SiteUtilities.GetPermaLinkUrl(nextEntry);
                        }
                        return nextLink;
                    }
                }
            }

            return new LiteralControl("");
        }

        public virtual Control PreviousLink(string previousNavigationText)
        {
            return PreviousLink(previousNavigationText, int.MaxValue);
        }

        /// <summary>
        /// Returns a Hyperlink control linking to the previous entry to this one.  
        /// The macro is of the form &lt;%previousLink("[Your String Here]",[Max Length of Title])%&gt;
        /// </summary>
        /// <param name="previousNavigationText">Whatever text should prefix the previous entry title
        /// (for example, double left angle brackets.)</param>
        /// <param name="maxLength"></param>
        /// <remarks>The text of this link is
        /// of the form "[previousNavigationText][Entry Title]" (spaces are inserted at the choice of the user).
        /// This string is not HTML-encoded so as to allow the user to insert HTML 
        /// blocks (e.g. images, etc.) in place of text if they so choose.</remarks>
        public virtual Control PreviousLink(string previousNavigationText, int maxLength)
        {
            // We only try to create a previous link in a situation where the user has loaded
            // a page with only one entry (i.e., a specific link), because this is where previous
            // and next links come in handy.
            // If the user, for example, is on the front page or a category page, the "previous" and "next" links
            // have no real point.
            if (requestPage.WeblogEntries.Count == 1)
            {
                EntryCollection cachedEntryCollection = requestPage.DataService.GetEntries(false);

                // Get our current entry from the cache.
                Entry ourCachedEntry = cachedEntryCollection[entry.EntryId];
                int ourCachedEntryIndex = cachedEntryCollection.IndexOf(ourCachedEntry);

                // The data service is sorted in reverse order, so to get the "next post" we need to look at 
                // the entry with an index 1 lower than our current entry.
                if (ourCachedEntryIndex < cachedEntryCollection.Count - 1)
                {
                    int previousEntryIndex = ourCachedEntryIndex + 1;
                    Entry previousEntry;

                    // The dataservice will not retrieve entries that have not been published.  So, if we 
                    // grab an id of an entry that hasn't been published yet, we need to go further up the list
                    // to get the previous link.  If there are no previous posts that have been published, there's
                    // no need to render the previous control.
                    do
                    {
                        previousEntry = cachedEntryCollection[previousEntryIndex];
                        ++previousEntryIndex;
                    } while (previousEntry == null && previousEntryIndex < cachedEntryCollection.Count - 1);

                    if (previousEntry != null)
                    {
                        HyperLink previousLink = new HyperLink();
                        previousLink.Text = previousNavigationText + Util.TruncateString(previousEntry.Title, maxLength);
                        if (previousEntry.Link != null)
                        {
                            previousLink.NavigateUrl = previousEntry.Link;
                        }
                        else
                        {
                            previousLink.NavigateUrl = SiteUtilities.GetPermaLinkUrl(previousEntry);
                        }

                        return previousLink;
                    }
                }
            }

            return new LiteralControl("");
        }

        /// <summary>
        /// Returns a separator string.  This would typically be used in-between a previous entry link and a next
        /// entry link, but could be used for any purpose on a permalink page that is not the earliest or most
        /// recent post.
        /// Takes the format &lt;%previousNextSeparator("[your string here]")%&gt;.  This string is not HTML-encoded so
        /// as to allow the user to insert HTML blocks (e.g. images, etc.) in place of text if they so choose.
        /// </summary>
        /// <param name="separatorText">Text that will be displayed as the separator.</param>
        /// <returns></returns>
        public virtual Control PreviousNextSeparator(string separatorText)
        {
            string separator = "";

            if (requestPage.WeblogEntries.Count == 1 && separatorText != null)
            {
                EntryCollection cachedEntryCollection = requestPage.DataService.GetEntries(false);

                // Check if our entry is either the very first entry or the very last entry, 
                // in which case no separator is needed for display.  

                Entry currentEntry = cachedEntryCollection[entry.EntryId];
                int currentCachedEntryIndex = cachedEntryCollection.IndexOf(currentEntry);

                if (currentCachedEntryIndex > 0 &&
                    currentCachedEntryIndex < cachedEntryCollection.Count - 1)
                {
                    separator = separatorText;
                }
            }

            return new LiteralControl(separator);
        }

        protected virtual Control RenderCategoryLinks(bool asTechnoratiTags)
        {
            PlaceHolder categories = new PlaceHolder();
            string[] splitCategories = entry.GetSplitCategories();
            IBlogDataService dataService = requestPage.DataService;
            CategoryCacheEntryCollection allCategories = dataService.GetCategories();

            foreach (string cat in splitCategories)
            {
                string displayName = cat;
                CategoryCacheEntry categoryEntry;

                if ((categoryEntry = allCategories[cat]) != null)
                {
                    displayName = categoryEntry.DisplayName;
                }

                if (categories.Controls.Count > 0)
                {
                    categories.Controls.Add(new LiteralControl("&nbsp;|&nbsp;"));
                }

                HyperLink category = new HyperLink();
                category.CssClass = "categoryLinkStyle";
                category.Text = displayName;

                if (asTechnoratiTags)
                {
                    category.Attributes.Add("rel", "tag"); //For folks who care like Technorati
                    category.NavigateUrl = "http://www.technorati.com/tag/" +
                                           HttpContext.Current.Server.UrlEncode(displayName);
                }
                else
                {
                    category.NavigateUrl = SiteUtilities.GetCategoryViewUrl(cat);
                }

                categories.Controls.Add(category);
            }
            return categories;
        }

        #endregion

        #region Lists

        /// <summary>
        /// A placeholder to render a list of hyperlinks.
        /// Corresponds to the macro: &lt;%trackbackList%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML (hyperlinks). Does not include Referrals. To render Referrals see <see cref="ReferralList"/>
        /// </remarks>
        public virtual Control TrackbackList
        {
            get
            {
                PlaceHolder placeHolder = new PlaceHolder();

                if (requestPage.ShowTrackingDetail)
                {
                    TrackingCollection trackings = requestPage.DataService.GetTrackingsFor(entry.EntryId);

                    if (trackings.Count > 0)
                    {
                        HtmlGenericControl trkSpan = new HtmlGenericControl("span");

                        placeHolder.Controls.Add(trkSpan);
                        trkSpan.Controls.Add(
                            new LiteralControl(SharedBasePage.CoreStringTables.GetString("text_trackings") + "<br />"));

                        foreach (Tracking trk in trackings)
                        {
                            if (trk.TrackingType == TrackingType.Pingback | trk.TrackingType == TrackingType.Trackback)
                            {
                                HyperLink hl = new HyperLink();
                                hl.CssClass = "trackbackLinkStyle";
                                hl.NavigateUrl = trk.PermaLink;

                                if (trk.RefererTitle != null && trk.RefererTitle.Length > 0 &&
                                    trk.RefererBlogName != null && trk.RefererBlogName.Length > 0)
                                {
                                    hl.Text = String.Format("\"{0}\" ({1})", trk.RefererTitle, trk.RefererBlogName);
                                }
                                else if (trk.RefererBlogName != null && trk.RefererBlogName.Length > 0)
                                {
                                    hl.Text = trk.RefererBlogName;
                                }
                                else if (trk.RefererTitle != null && trk.RefererTitle.Length > 0)
                                {
                                    hl.Text = trk.RefererTitle;
                                }
                                else
                                {
                                    hl.Text = trk.PermaLink;
                                }

                                // Referral links can get very long, so clip them.
                                hl.Text = SiteUtilities.ClipString(hl.Text, 80);

                                trkSpan.Controls.Add(hl);

                                // allow the user to delete the trackback
                                if (!requestPage.HideAdminTools && SiteSecurity.IsInRole("admin"))
                                {
                                    // need to url encode the url
                                    string url = trk.PermaLink.Replace("&", "%26");

                                    hl = new HyperLink();
                                    hl.CssClass = "deleteLinkStyle";
                                    Image img = new Image();
                                    img.CssClass = "deleteLinkImageStyle";
                                    img.ImageUrl =
                                        new Uri(new Uri(SiteUtilities.GetBaseUrl(requestPage.SiteConfig)),
                                                requestPage.GetThemedImageUrl("deletebutton")).ToString();
                                    img.BorderWidth = 0;
                                    hl.Controls.Add(img);
                                    hl.NavigateUrl = String.Format("javascript:deleteReferral('{0}', '{1}', '{2}')",
                                                                   entry.EntryId,
                                                                   url,
                                                                   trk.TrackingType == TrackingType.Trackback
                                                                       ? ("Trackback")
                                                                       : ("Pingback"));
                                    trkSpan.Controls.Add(hl);
                                }

                                trkSpan.Controls.Add(
                                    new LiteralControl("&nbsp;[" + trk.TrackingType + "]<br />"));
                            }
                        }

                        if (trkSpan.Controls.Count > 1)
                        {
                            return placeHolder;
                        }
                        else
                        {
                            return null;
                        }
                    }
                }

                return null;
            }
        }

        /// <summary>
        /// A placeholder to render a list of hyperlinks.
        /// Corresponds to the macro: &lt;%trackbackList%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML (hyperlinks).
        /// </remarks>
        public virtual Control ReferralList
        {
            get { return ReferralListFiltered(Int32.MaxValue, false, false); }
        }

        /// <summary>
        /// A placeholder to render a list of hyperlinks.
        /// Corresponds to the macro: &lt;%relatedPostList%&gt;
        /// </summary>
        /// <remarks>
        /// The output is HTML (hyperlinks).
        /// </remarks>
        public virtual Control RelatedPostList
        {
            get
            {
                PlaceHolder placeHolder = new PlaceHolder();

                if (requestPage.ShowTrackingDetail)
                {
                    string[] splitCategories = entry.GetSplitCategories();
                    IBlogDataService dataService = requestPage.DataService;
                    CategoryCacheEntryCollection allCategories = dataService.GetCategories();
                    EntryCollection entries = new EntryCollection();
                    ArrayList entryIds = new ArrayList();
                    entryIds.Add(entry.EntryId);

                    foreach (string cat in splitCategories)
                    {
                        string displayName = cat;
                        CategoryCacheEntry categoryEntry;
                        if ((categoryEntry = allCategories[cat]) != null)
                        {
                            displayName = categoryEntry.DisplayName;
                        }

                        entries.AddRange(dataService.GetEntriesForCategory(displayName, null));
                    }

                    if (entries.Count > 0)
                    {
                        HtmlGenericControl relatedPosts = new HtmlGenericControl("span");

                        placeHolder.Controls.Add(relatedPosts);
                        relatedPosts.Controls.Add(
                            new LiteralControl(SharedBasePage.CoreStringTables.GetString("text_related_posts") +
                                               "<br />"));

                        int count = 0;
                        entries.Sort(new EntrySorter());

                        foreach (Entry relatedPost in entries)
                        {
                            if (entryIds.Contains(relatedPost.EntryId))
                            {
                                continue;
                            }

                            if (count++ > 5)
                            {
                                break;
                            }

                            entryIds.Add(relatedPost.EntryId);

                            HyperLink link = new HyperLink();
                            link.CssClass = "relatedPostLinkStyle";
                            link.Text = relatedPost.Title;

                            if (relatedPost.Link != null)
                            {
                                link.NavigateUrl = relatedPost.Link;
                            }
                            else
                            {
                                link.NavigateUrl = SiteUtilities.GetPermaLinkUrl(relatedPost);
                            }

                            relatedPosts.Controls.Add(link);
                            relatedPosts.Controls.Add(new LiteralControl("<br />"));
                        }

                        return placeHolder;
                    }
                }

                return null;
            }
        }

        /// <summary>
        /// Returns the list of trackbacks embedded in a div element.
        /// </summary>
        public virtual Control TrackbackListDiv(string divClass)
        {
            Control trackbacks = TrackbackList;

            if (trackbacks == null)
            {
                return new LiteralControl();
            }

            HtmlGenericControl div = new HtmlGenericControl("div");
            div.Controls.Add(trackbacks);

            if (divClass != null && divClass.Length > 0)
            {
                div.Attributes.Add("class", divClass);
            }

            return div;
        }

        public virtual Control ReferralListFiltered(int maxReferrals, bool excludeOnlineAggregators,
                                                    bool excludeSearchEngines)
        {
            PlaceHolder placeHolder = new PlaceHolder();

            if (requestPage.ShowTrackingDetail && requestPage.SiteConfig.EnableEntryReferrals)
            {
                TrackingCollection trackings = requestPage.DataService.GetTrackingsFor(entry.EntryId);

                if (trackings.Count > 0)
                {
                    placeHolder.Controls.Add(new LiteralControl("<br />"));
                    HtmlGenericControl referralSpan = new HtmlGenericControl("span");
                    HtmlGenericControl referralSpanHidden = new HtmlGenericControl("span");
                    referralSpanHidden.Style.Add("display", "none");
                    referralSpanHidden.ID = "referralSpanHidden";

                    HtmlAnchor anchor = new HtmlAnchor();
                    anchor.HRef = "referralList";

                    placeHolder.Controls.Add(referralSpan);
                    referralSpan.Controls.Add(anchor);
                    referralSpan.Controls.Add(new LiteralControl("Referred by:<br />"));
                    referralSpan.Controls.Add(referralSpanHidden);

                    int count = 0;

                    foreach (Tracking trk in trackings)
                    {
                        if (trk.TrackingType == TrackingType.Referral | trk.TrackingType == TrackingType.Unknown)
                        {
                            // do not render a referral if it's in the filtered list
                            if (excludeOnlineAggregators &&
                                trk.PermaLink.IndexOf("bloglines.com") > -1 |
                                trk.PermaLink.IndexOf("newsgator.com") > -1)
                            {
                                continue;
                            }

                            if (excludeSearchEngines &&
                                trk.PermaLink.IndexOf("q=") > -1 | trk.PermaLink.IndexOf("p=") > -1)
                            {
                                continue;
                            }

                            // if this is a referral from a search engine, make it PrettyPrint
                            HyperLink hl = SiteUtilities.ParseSearchString(trk.PermaLink);

                            if (hl == null)
                            {
                                hl = new HyperLink();
                                hl.NavigateUrl = trk.PermaLink;

                                if (trk.RefererTitle != null && trk.RefererTitle.Length > 0 &&
                                    trk.RefererBlogName != null && trk.RefererBlogName.Length > 0)
                                {
                                    hl.Text = String.Format("\"{0}\" ({1})", trk.RefererTitle, trk.RefererBlogName);
                                }
                                else if (trk.RefererBlogName != null && trk.RefererBlogName.Length > 0)
                                {
                                    hl.Text = trk.RefererBlogName;
                                }
                                else if (trk.RefererTitle != null && trk.RefererTitle.Length > 0)
                                {
                                    hl.Text = trk.RefererTitle;
                                }
                                else
                                {
                                    hl.Text = trk.PermaLink;
                                }

                                // Referral links can get very long, so clip them.
                                hl.Text = SiteUtilities.ClipString(hl.Text, 60);
                            }

                            hl.CssClass = "trackbackLinkStyle";
                            hl.Attributes.Add("rel", "nofollow");

                            // allow the user to delete the referral
                            HyperLink deleteHyperlink = null;

                            if (!requestPage.HideAdminTools && SiteSecurity.IsInRole("admin"))
                            {
                                // need to url encode the url
                                string url = HttpUtility.UrlEncode(trk.PermaLink);
                                deleteHyperlink = new HyperLink();
                                deleteHyperlink.CssClass = "deleteLinkStyle";
                                Image img = new Image();
                                img.CssClass = "deleteLinkImageStyle";
                                img.ImageUrl =
                                    new Uri(new Uri(SiteUtilities.GetBaseUrl(requestPage.SiteConfig)),
                                            requestPage.GetThemedImageUrl("deletebutton")).ToString();
                                img.BorderWidth = 0;
                                deleteHyperlink.Controls.Add(img);
                                deleteHyperlink.NavigateUrl =
                                    String.Format("javascript:deleteReferral('{0}', '{1}', 'Referral')", entry.EntryId,
                                                  url);
                            }

                            if (count > maxReferrals)
                            {
                                referralSpanHidden.Controls.Add(hl);
                                if (deleteHyperlink != null)
                                {
                                    referralSpanHidden.Controls.Add(deleteHyperlink);
                                }
                                referralSpanHidden.Controls.Add(
                                    new LiteralControl("&nbsp;[" + trk.TrackingType + "]<br />"));
                            }
                            else
                            {
                                referralSpan.Controls.Add(hl);
                                if (deleteHyperlink != null)
                                {
                                    referralSpan.Controls.Add(deleteHyperlink);
                                }
                                referralSpan.Controls.Add(
                                    new LiteralControl("&nbsp;[" + trk.TrackingType + "]<br />"));
                                count++;
                            }
                        }
                    }

                    if (count > maxReferrals)
                    {
                        //SDH: 2005-01-20. This is going to be a problem, since ASP.NET hasn't realized yet, 
                        // due to the late-bound nature of our Macros, that the span and hyperlink are going
                        // to be within a container control in CommentView. We may want
                        // to change the getElementById() call to search for elements that ENDWITH what we are
                        // looking for. Until then this code below won't work in CommentView.aspx.
                        //OmarS: 2005-01-30. I fixed this by looping through the elements
                        referralSpan.Controls.Add(new LiteralControl("<br />"));
                        HyperLink hl = new HyperLink();
                        referralSpan.Controls.Add(hl);
                        hl.Text = SharedBasePage.CoreStringTables.GetString("text_referrallist_more");
                        hl.ID = "referralMore";
                        //string script = "document.getElementById('"+ referralSpanHidden.ClientID + "').style.display='inline';";
                        //script += "document.getElementById('" + hl.ClientID + "').style.display='none';";

                        hl.NavigateUrl = "javascript:showReferral()";
                        //hl.Attributes.Add("onclick", script);
                    }
                }
            }

            return placeHolder;
        }

        /// <summary>
        /// Returns the list of related posts embedded in a div element.
        /// </summary>
        public virtual Control RelatedPostListDiv(string divClass)
        {
            Control related = RelatedPostList;
            if (related == null)
            {
                return new LiteralControl();
            }

            HtmlGenericControl div = new HtmlGenericControl("div");
            div.Controls.Add(related);

            if (divClass != null && divClass.Length > 0)
            {
                div.Attributes.Add("class", divClass);
            }

            return div;
        }

        #endregion

        #region OnPageNavigation

        public virtual Control OnPageHomeLink(string navigationText)
        {
            return OnPageHomeLink(navigationText, "home", null);
        }

        public virtual Control OnPageHomeLink(string navigationText, string cssClass, string removeUrlFragmentRegex)
        {
            if (requestPage.WeblogEntries.Count == 1)
            {
                HyperLink homeLink = new HyperLink();
                homeLink.Controls.Add(LocalString(navigationText));
                homeLink.NavigateUrl =
                    homeLink.NavigateUrl =
                    String.Format("{0}",
                                  GetClientUrl(SiteUtilities.GetStartPageUrl(requestPage.SiteConfig),
                                               removeUrlFragmentRegex));
                homeLink.CssClass = cssClass;
                return homeLink;
            }

            return new PlaceHolder();
        }

        public virtual Control OnPageNextLink(string navigationText)
        {
            return OnPageNextLink(navigationText, "next", null);
        }

        public virtual Control OnPageNextLink(string navigationText, string removeUrlFragmentRegex)
        {
            return OnPageNextLink(navigationText, "next", removeUrlFragmentRegex);
        }

        public virtual Control OnPageNextLink(string navigationText, string cssClass, string removeUrlFragmentRegex)
        {
            if (requestPage.WeblogEntries.Count <= 1)
            {
                return new LiteralControl();
            }

            int currentIndex = requestPage.WeblogEntries.IndexOf(entry);

            if (currentIndex > 0)
            {
                Entry nextEntry = requestPage.WeblogEntries[currentIndex - 1];

                HyperLink nextLink = new HyperLink();
                nextLink.Controls.Add(LocalString(navigationText));
                nextLink.NavigateUrl =
                    String.Format("{0}#a{1}", GetClientUrl(HttpContext.Current.Request.RawUrl, removeUrlFragmentRegex),
                                  nextEntry.EntryId);
                nextLink.CssClass = cssClass;
                return nextLink;
            }

            return new LiteralControl();
        }

        public virtual Control OnPagePreviousLink(string navigationText)
        {
            return OnPagePreviousLink(navigationText, "previous", null);
        }

        public virtual Control OnPagePreviousLink(string navigationText, string removeUrlFragmentRegex)
        {
            return OnPagePreviousLink(navigationText, "previous", removeUrlFragmentRegex);
        }

        public virtual Control OnPagePreviousLink(string navigationText, string cssClass, string removeUrlFragmentRegex)
        {
            if (requestPage.WeblogEntries.Count <= 1)
            {
                return new LiteralControl();
            }

            int currentIndex = requestPage.WeblogEntries.IndexOf(entry);

            if (currentIndex < requestPage.WeblogEntries.Count - 1)
            {
                Entry previousEntry = requestPage.WeblogEntries[currentIndex + 1];

                HyperLink previousLink = new HyperLink();
                previousLink.Controls.Add(LocalString(navigationText));
                previousLink.NavigateUrl =
                    String.Format("{0}#a{1}", GetClientUrl(HttpContext.Current.Request.RawUrl, removeUrlFragmentRegex),
                                  previousEntry.EntryId);
                previousLink.CssClass = cssClass;
                return previousLink;
            }

            return new LiteralControl();
        }

        #endregion
    }
}